﻿using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Xml;
using MathService.Exceptions;
using MathService.Service;

namespace System
{
    public static class UriExtentions
    {

        public static int InternetConnectionTimeout { get; set; }
        public static int ReconnectTryCount { get; set; }
        static UriExtentions()
        {
            InternetConnectionTimeout = 10000;
            ReconnectTryCount = 5;
        }

        public static bool CheckConnection(this Uri uri)
        {
            using(var client = new TcpClient())
                try { client.Connect(uri.Host, uri.Port); return true; } catch { return false; }
        }

        public static PingReply Ping(this Uri uri, int BufferLength = 32, bool DontFragment = true, int Timeout = 120)
        {
            using(var ping = new Ping())
            {
                var options = new PingOptions { DontFragment = DontFragment };
                var buffer = new byte[BufferLength];

                return ping.Send(uri.Host, Timeout, buffer, options);
            }
        }

        public static PingReply[] Ping(this Uri uri, int Count,
            int BufferLength = 32, bool DontFragment = true, int Timeout = 120,
            Action<int, int, PingReply> OnPing = null)
        {
            var result = new PingReply[Count];
            var buffer = new byte[BufferLength];
            using(var ping = new Ping())
                for(var i = 0; i < Count; i++)
                {
                    var options = new PingOptions { DontFragment = DontFragment };

                    result[i] = ping.Send(uri.Host, Timeout, buffer, options);
                    if(OnPing != null) OnPing(i, Count, result[i]);
                }
            return result;
        }

        public static string GetText(this Uri uri) { return GetText(uri, Encoding.UTF8); }
        public static string GetText(this Uri uri, Encoding encoding)
        {
            using(var client = new WebClient { Encoding = encoding })
                return client.DownloadString(uri);
        }

        public static event ExceptionEventHandler<ConnectionException> ConnectionTimeouted;
        public static Stream GetStream(this Uri uri, int Timeout = -1)
        {
            if(Timeout != 0)
            {
                var i = 0;
                Exception LastError = null;
                using(var watcher = new ThreadWatcher(Timeout == -1 ? InternetConnectionTimeout : Timeout).InitializeObject(w => w.ThreadEvent += (s, e) => e.Argument.Abort("Timeout")))
                    while(i < ReconnectTryCount)
                        using(var client = new WebClient())
                            try
                            {
                                watcher.Start();
                                return client.OpenRead(uri);
                            } catch(ThreadAbortException AbortException)
                            {
                                Thread.ResetAbort();
                                i++;
                                LastError = AbortException;
                            } catch(WebException WebError)
                            {
                                Thread.ResetAbort();
                                i++;
                                LastError = WebError;
                            } catch(Exception error)
                            {
                                Thread.ResetAbort();
                                i++;
                                LastError = error;
                            }
                var arg = new ExceptionEventHandlerArgs<ConnectionException>(
                            new ConnectionException("Ошибка подключения к " + uri, LastError));
                ConnectionTimeouted.ThrowIfUnhandled(null, arg);
                return null;
            }
            using(var client = new WebClient())
                return client.OpenRead(uri);
        }

        public static IPHostEntry GetDNSAddress(this Uri uri) { return Dns.GetHostEntry(uri.Host); }

        [Pure]
        public static XmlDocument GetXmlDocument(this Uri uri, int Timeout = -1)
        {
            using(var stream = uri.GetStream(Timeout))
                return new XmlDocument().InitializeObject(d => d.Load(stream));
        }

        [Pure]
        public static XmlNode GetXmlNode(this Uri uri, string XPath, int Timeout = -1)
        {
            return uri.GetXmlDocument(Timeout).SelectSingleNode(XPath);
        }

        [Pure]
        public static IEnumerable<XmlNode> GetXmlNodes(this Uri uri, string XPath, int Timeout = -1)
        {
            var nodes = uri.GetXmlDocument(Timeout).SelectNodes(XPath);
            return nodes != null ? nodes.Cast<XmlNode>() : null;
        }

        public static Uri AddQuery(this Uri BaseUri, string RequestString)
        {
            return new Uri(BaseUri, string.Format("{1}{0}", RequestString,
                string.IsNullOrEmpty(BaseUri.Query) ? "?" : "&"));
        }
    }
}